# square_rain.py
#
# Square Rain - an arcade game
# Copyright (C) 2006 Avinash 'aetherFox' Vora
#
# This source is protected by the GPL
# See readme.txt for details

import sys, random, time, math, base64
import pygame
from pygame.locals import *

# public variables
game_title = "Square Rain"

screen_size = width, height = [320, 240]
tile_size = tile_width, tile_height = [16, 16]
max_tiles = max_tiles_x, max_tiles_y = [width / tile_width, height / tile_height]

max_fps = 60
delay = 1000 / max_fps

display_flags = 0

score = 0
level = 1

min_speed = 2
max_speed = 4
max_boxes = 10

white = [255, 255, 255]

# seed the mersenne twister
random.seed()

class game_base:
	def __init__(self):
		# initializing function
		pygame.init()
		
		# create the display surface
		self.screen = pygame.display.set_mode(screen_size, display_flags)
		
		# modify the window
		pygame.display.set_caption(game_title)

class box_base(pygame.sprite.Sprite):
	def __init__(self, game_base):
		# initialize variables
		size = tile_size
		
		# initialize the pygame.sprite.Sprite to build on
		pygame.sprite.Sprite.__init__(self)
		
		# create the image
		self.image = pygame.Surface(size)
		self.image.fill(self.color)
		self.rect = self.image.get_rect()
		
		# call the function to draw the box
		self.draw(self.position, game_base.screen)
	
	def draw(self, position, screen):
		# get the new position and blit it
		self.rect.topleft = position
		screen.blit(self.image, self.rect)
	
	def update_position(self):
		self.position = self.x, self.y
	
	def rand_bool(self):
		return random.randint(0, 1)

class player_base(box_base):
	# public variables
	position = x, y = [160, 160]
	speed = x_speed, y_speed = [4, 4]
	
	color = [200, 200, 200]
	
	def __init__(self, game_base):
		# initialize the box_base to build on
		box_base.__init__(self, game_base)
	
	def parse_input(self):
		event = pygame.event.poll()
		
		# handle different key presses
		# boundary collision checking
		if pygame.key.get_pressed()[K_RIGHT]:
			if (self.rect.right < width): 
				self.x += self.x_speed
		if pygame.key.get_pressed()[K_LEFT]:
			if (self.rect.left > 0):
				self.x -= self.x_speed
		if pygame.key.get_pressed()[K_DOWN]:
			if (self.rect.bottom < height):
				self.y += self.y_speed
		if pygame.key.get_pressed()[K_UP]:
			if (self.rect.top > 0):
				self.y -= self.y_speed
		
		# update position
		self.update_position()

class enemy_base(box_base):
	color = [255, 0, 0]
	
	def __init__(self, game_base):
		# initialize position and speed
		self.position = self.x, self.y = [random.randint(0, max_tiles_x - 1) * tile_width, -tile_height]
		self.speed = self.speed_x, self.speed_y = [random.randint(min_speed, max_speed), random.randint(min_speed, max_speed)]
		
		# initialize the box_base to build on
		box_base.__init__(self, game_base)
		
		self.counter = random.randint(0, 100)
	
	def update(self):
		# check whether a random counter has run down before launching box
		if self.counter == 0:
			self.y += self.speed_y
			self.update_position()
		else:
			self.counter -= 1
			
class bonus_base(box_base):
	color = [255, 255, 255]
	
	def __init__(self, game_base):	
		# initialize position and speed
		self.position = self.x, self.y = [random.randint(0, max_tiles_x - 1) * tile_width, -tile_height]
		self.speed = self.speed_x, self.speed_y = [random.randint(min_speed, max_speed), random.randint(min_speed, max_speed)]
		
		# initialize the box_base to build on
		box_base.__init__(self, game_base)
		
		self.counter = random.randint(0, 100)
	
	def update(self):
		# check whether a random counter has run down before launching box
		if self.counter == 0:
			self.y += self.speed_y
			self.update_position()
		else:
			self.counter -= 1

class text_base(pygame.font.Font):
	def __init__(self, font_name, size):
		# initialize the pygame.font.Font to build on
		pygame.font.Font.__init__(self, font_name, size)
	
	def write(self, text, color):
		# write the text and create it's rect
		self.text = self.render(text, 1, color)
		self.rect = self.text.get_rect()
	
	def draw(self, position, screen):
		# draw the text to the screen
		self.rect.topleft = position
		screen.blit(self.text, self.rect)

def get_highscore():
	# try to get the high score or return 0
	try:
		file = open("hisco.dat", "rb")
		old_score = base64.b64decode(file.read())
		file.close()
		return old_score
	except IOError:
		return "0"

def check_highscore():
	# check the highscore and replace if less than score
	if score > int(get_highscore()):
		file = open("hisco.dat", "wb")
		file.write(base64.b64encode(str(score)))
		file.close()
	
if __name__ == "__main__": 
	# initialize classes
	game = game_base()
	
	# create text elements
	text_title = text_base("data/04b_11.ttf", 16)
	text = text_base("data/04b_11.ttf", 8)
	
	# initialize fps counter variables
	frames = 0
	ticks = pygame.time.get_ticks()
	
	# create intro enemy boxes
	boxes = pygame.sprite.Group()
	for i in range(max_boxes):
		boxes.add(enemy_base(game))
	
	# intro game loop
	while 1:
		# write title text
		text_title.write("square rain", white)
		text.write("high score " + get_highscore(), white)
		
		text_title.rect.center = [width / 2, height / 2]
		text.rect.center = [width / 2, (height / 2) + 20]
		
		# clear the screen
		game.screen.fill([0, 0, 0])
		
		# update the enemy boxes and redraw them
		for box in boxes.sprites():
			box.update()
			if box.rect.bottom >= height:
				boxes.remove(box)
				boxes.add(enemy_base(game))
			box.draw(box.position, game.screen)
		
		# draw texts
		game.screen.blit(text_title.text, text_title.rect)
		game.screen.blit(text.text, text.rect)
		
		# update the display
		pygame.display.update()
			
		# wait for input and start game
		event = pygame.event.poll()
		if (event.type == KEYDOWN):
			boxes.empty()
			break

	# create initial enemy boxes
	boxes = pygame.sprite.Group()
	for i in range(max_boxes):
		boxes.add(enemy_base(game))
			
	# initialize player
	player = player_base(game)
	
	# create initial bonus
	bonuses = pygame.sprite.Group()
	bonuses.add(bonus_base(game))
	
	# main game loop
	while 1:		
		# read events
		event = pygame.event.poll()
		if (event.type == pygame.QUIT) or (event.type == KEYDOWN and event.key == K_ESCAPE):
			# print fps and exit
			# print "fps:  %d" % ((frames * 1000) / (pygame.time.get_ticks() - ticks))
			sys.exit()
		elif (event.type == KEYDOWN and event.key == K_SPACE):
				pygame.time.delay(1000)
		
		# handle keyboard input
		player.parse_input()
		
		# check for collision with bonus, if found increase score
		if pygame.sprite.spritecollide(player, bonuses, 1):
			score += level * 20
		
		# check for collision, if found display 'game over'
		if pygame.sprite.spritecollide(player, boxes, 0):
			pygame.time.delay(500)
			
			check_highscore()
			
			text_title.write("game over", white)
			text.write("final score " + str(score), white)
			
			text_title.rect.center = [width / 2, height / 2]
			text.rect.center = [width / 2, (height / 2) + 20]
			
			game.screen.blit(text_title.text, text_title.rect)
			game.screen.blit(text.text, text.rect)
			
			pygame.display.update()
			
			# wait for input and exit
			while 1:
				# read events
				event = pygame.event.poll()
				if (event.type == KEYDOWN and event.key == K_ESCAPE):
					sys.exit()
		
		# increase level if the new score was reached
		if score >= (level * 100):
			level = level + 1
			if level % 2 == 0:
				min_speed += 1
				max_speed += 1
			max_boxes += 1
			
			# add a new bonus
			bonuses.add(bonus_base(game))
		
		# add a new enemy if the level was increased
		if len(boxes.sprites()) < max_boxes:
			boxes.add(enemy_base(game))
		
		# clear the screen
		game.screen.fill([0, 0, 0])
		
		# write score to the screen
		text.write("score " + str(score), white)
		
		# redraw the player
		player.draw(player.position, game.screen)
		
		# update the enemy boxes and redraw them
		for box in boxes.sprites():
			box.update()
			if box.rect.bottom >= height:
				score += level * 1
				boxes.remove(box)
				boxes.add(enemy_base(game))
			box.draw(box.position, game.screen)
		
		# update the bonuses and redraw them
		for bonus in bonuses.sprites():
			bonus.update()
			if bonus.rect.bottom >= height:
				bonuses.remove(bonus)
			bonus.draw(bonus.position, game.screen)
		
		# draw the text for the score
		text.draw([10, 10], game.screen)	
		
		# update the screen
		pygame.display.update()
		
		# add a time delay and update frames counter
		pygame.time.delay(delay)
		# frames = frames + 1